//
// Created by LiuYou on 2021/11/25.
//

#ifndef HTTP_SERVER_HTTPCONN_HPP
#define HTTP_SERVER_HTTPCONN_HPP


#include <netinet/in.h>
#include <sys/stat.h>
#include <cstring>
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <sys/mman.h>
#include <cstdarg>
#include <sys/uio.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <unistd.h>

// http请求方法。
#include "Enum/Method.hpp"
// 服务器解析http请求报文的结果。
#include "Enum/RequestMessageParseResult.hpp"
// 主状态机。
#include "Enum/RequestMessage.hpp"
// 从状态机。
#include "Enum/RequestMessagePerLineParseResult.hpp"
// epoll封装。
#include "../../Epoll/Epoll.hpp"


/* ***** 这些常量字符串后期会重构为一个名可能是 HttpResponse 的类。 ***** */

static const char* const OK_200_TITLE = "OK";

static const char* const ERROR_400_TITLE = "Bad Request";
// 您的请求语法错误或本质上无法满足。
static const char* const ERROR_400_FORM = "Your request has bad syntax or is inherently impossible to satisfy.\n";

static const char* const ERROR_403_TITLE = "Forbidden";
// 您无权从此服务器获取文件。
static const char* const ERROR_403_FORM = "You do not have permission to get file from this server.\n";

static const char* const ERROR_404_TITLE = "Not Found";
// 在此服务器上找不到请求的文件。
static const char* const ERROR_404_FORM = "The requested file was not found on this server.\n";

static const char* const ERROR_500_TITLE = "Internal Error";
// 提供请求的文件时出现异常问题。
static const char* const ERROR_500_FORM = "There was an unusual problem serving the requested file.\n";

/* ************************************************************* */


/**
 * @brief HttpConn类封装了一个http连接的现场。
 *
 * @note
 * <p>1. 没有考虑异常。</p>
 * <p>2. 后期可能会将这个类中的字段和方法再封装为几个类。</p>
 * @since 0.1
 * @author ly
 * @date 2021-11-25
 */
class HttpConn {
public:
    /**
     * @brief 初始化一个 http 连接。应由 main线程 来调用。
     * @param connFd 与这个http连接对应的socket文件描述符。
     * @param clientAddress 与这个http连接对应的客户端的地址。
     *
     * @since 0.1
     */
    void initConn(int connFd, const sockaddr_in& clientAddress) {
        this->connFd = connFd;
        this->clientAddress = clientAddress;

        int reuse = 1;
        setsockopt( this->connFd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof( reuse ));

        Epoll::addFdToEpoll( epollFd, this->connFd, true );
        ++userCount;

        Init();
    }

private:

    //region 一个http请求报文。
    //      GET /index.html HTTP/1.1
    //      Host: 192.168.52.138
    //      User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:94.0) Gecko/20100101 Firefox/94.0
    //      Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
    //      Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
    //      Accept-Encoding: gzip, deflate
    //      Connection: keep-alive
    //      Upgrade-Insecure-Requests: 1
    //      If-Modified-Since: Sun, 14 Nov 2021 08:02:52 GMT
    //      If-None-Match: "2aa6-5d0bb1e82f32f-gzip"
    //      Cache-Control: max-age=0
    //endregion

    /**
     * @brief 供 接口 initConn();调用。
     * 初始化一个 HttpConn对象 的内部字段。
     *
     * @since 0.1
     */
    void Init() {
        // 初始化与读相关的缓冲区及辅助的变量。
        memset( readBuf, '\0', sizeof( readBuf ));
        readBufEnd = 0;
        parseReadBufIdx = 0;
        requestMessagePerLineBegin = 0;

        // 初始化主状态机的状态为 请求行。
        state = RequestMessage::REQUEST_LINE;

        // GET /index.html HTTP/1.1
        method = Method::GET;
        url = nullptr;
        memset( requestFilePath, '\0', sizeof( requestFilePath ));
        httpVersion = nullptr;
        // Host: 192.168.52.138
        host = nullptr;
        // Connection: keep-alive
        isKeepAlive = false;
        /*
         * Content-Length: 字段
         * 如果请求报文有该字段, 那就可能不是GET方法, 可能是POST方法。
         */
        requestMessageContentLen = 0;

        // requestFileAddress, 这个字段由 processRequestFile();函数来填充。
        // requestFileStat, 这个字段由 processRequestFile();函数来填充。


        memset( writeBuf, '\0', sizeof( writeBuf ));
        writeIdx = 0;

        // ioVec[2], 这个字段由 processHttpResponse();函数来填充。
        // ioVecCount, 这个字段由 processHttpResponse();函数来填充。
        // bytesToSend, 这个字段由 processHttpResponse();函数来填充。
        // bytesHaveSend, 这个字段由 processHttpResponse();函数来填充。
    }

public:
    /**
     * @brief 关闭一个 socket 连接。
     * @param realClose
     *
     * @note 关闭一个 socket 连接, 但是不会调用 close(); 为什么我也不知道。仅仅是将 connFd赋值为 -1 。
     * @bug 即上述 note 的内容。
     *
     * @since 0.1
     */
    void closeConn(bool realClose = true) {
        if (realClose && (connFd != -1)) {
            Epoll::deleteFdFromEpoll( epollFd, connFd );
            fprintf( stdout, "%d 关联的浏览器客户端关闭了连接。\n", connFd );

            // todo 笔记: 这里缺少了关闭 fd 的操作。
//            close( connFd );

            connFd = -1;
            --userCount;
            fprintf( stdout, "当前还在的连接, 当前有 %d 个用户。\n", HttpConn::userCount );
        }
    }

public:
    /*
    recv();函数的返回值 man手册说明:
    RETURN VALUE
        These calls return the number of bytes received, or -1 if an error occurred.  In the event of an error, errno is set to indicate the error.
        这些调用返回接收到的字节数，如果发生错误则返回-1。如果发生错误，则设置errno来指示错误。

        When a stream socket peer has performed an orderly shutdown, the return value will be 0 (the traditional "end-of-file" return).
        当流套接字对等端执行有序关闭时，返回值将为 0（传统的“文件结束”返回）。

        Datagram sockets in various domains (e.g., the UNIX and Internet domains) permit zero-length datagrams.  When such a datagram is received, the return value is
        0.
        各种域（例如 UNIX 和 Internet 域）中的数据报套接字允许零长度数据报。当收到这样的数据报时，返回值为 0。

        The value 0 may also be returned if the requested number of bytes to receive from a stream socket was 0.
        如果从流套接字接收的请求字节数为0，也可以返回值0。

    ERRORS(部分)
       EAGAIN or EWOULDBLOCK
              The  socket is marked nonblocking and the receive operation would block, or a receive timeout had been set and the timeout expired before data was received.  POSIX.1
              allows either error to be returned for this case, and does not require these constants to have the same value, so a portable application should check for both possi‐
              bilities.
              套接字被标记为非阻塞并且接收操作将阻塞，或者已经设置了接收超时并且在接收到数据之前超时到期。
              POSIX.1 允许在这种情况下返回任一错误，并且不要求这些常量具有相同的值，因此可移植应用程序应该检查这两种可能性。


     */
    /**
     * @brief main线程读取 http报文, 直到客户端不再发送为止。将读取到的 http报文存储在字段 readBuf缓冲区中。
     * @details 因为连接的socket, 即字段 connFd 为 非阻塞的文件描述符。 所以, 对于 非阻塞IO, 当 recv();调用
     * 发生错误返回 -1, 即 < 0。并不一定是真错了, 需要判断 errno 如果是 EAGAIN 或者 EWOULDBLOCK。那就是读完了。
     *
     * @return 函数是否调用成功。
     *
     * @since 0.1
     */
    bool read() {
        if (readBufEnd >= sizeof( readBuf )) {
            return false;
        }

        // 因为采用的是 epoll one shot, 所以在 main线程中必须读到客户端不再发送 http请求报文为止。

        // 循环读取浏览器发来的数据, 直到无数据可读或者对方关闭连接。
        // 将读取到的http请求报文读取到readBuf中, 并更新readIdx(readBufEnd)为读取到的在readBuf中最后一个字节的下一个位置。
        while (true) {
            // 坚持读够所有的字节, 即将数据一次性读取到 readBuf 中。
            ssize_t rcvBytes = recv( connFd, readBuf + readBufEnd, sizeof( readBuf ) - readBufEnd, 0 );
            if (rcvBytes < 0) {
                // 因为是Epoll非阻塞ET模式下(非LT模式下)，需要一次性将数据读完
                // 如果没有数据可读了, 那就是读取完毕, 退出。
                if (errno == EAGAIN || errno == EWOULDBLOCK) {
                    break;
                }
            }
                // 如果socket客户端关闭了, 或者客户端不再发送请求报文了, 那就返回读取错误。
                // 这样就不会在 main线程中, 往线程池中扔任务。然后在回到 epoll_wait();等待浏览器发送 http请求报文。
            else if (rcvBytes == 0) {
                return false;
            }
            readBufEnd += (int) rcvBytes;

        }

        // 如果读取了当前的 epoll one shot 下的一个http请求报文, 而且在读取过程中没有新的http请求报文, 那就返回真。
        // 如果 main 线程在读 一个http请求报文的同时, 有来了一个请求报文, 因为是 one shot, 那就是会读两个请求报文。
        return true;
    }

public:
    /*
     * 线程池中的 8 个工作线程调用。
     */
    /**
     * @brief <strong>该函数由线程池中的 8 个工作线程调用。</strong>工作线程通过此函数完成对http请求报文的解析, 以及
     * 完成将http响应报文写入到字段 writeBuf缓冲区。
     *
     * <strong>此函数为核心函数。</strong>
     *
     * @details 这个函数内部调用了以下两个函数:
     * @fn ParseHttpRequest(); @endcode
     * 此函数为解析http请求报文的主状态机, 解析好后填充了 HttpConn类的一些相关字段(例如: url、
     * requestFilePath、httpVersion、host、isKeepAlive、requestMessageContentLen、requestFileStat等)。
     * 函数最后返回解析处理http请求报文的结果。该结果传参到processHttpResponse();函数, 然后对http请求进行响应。
     *
     * @fn ProcessHttpResponse(); @endcode 这函数为对http请求报文解析处理的结果进行响应。函数内部会填充(bytesToSend、
     * bytesHaveSend、ioVec、ioVecCount、writeBuf字段)。该函数的返回值为 bool 类型, 如果返回为真, 即响应报文处理好了,
     * 将 connFd 在 epoll 中注册的事件修改为 EPOLLOUT。然后 main线程 负责将相应报文通过 TCP来发送到浏览器客户端。
     *
     *
     * @note 注意: 这两个函数内部调用了其它函数, 如下所示:
     * @fn ParseHttpRequest(); @endcode 函数内部调用了
     *      @fn ParseRequestMessagePerLine(); @endcode
     *      此函数为从状态机。负责解析readBuf中的http请求报文的每一行。
     *      @fn ParseRequestLine(); @endcode
     *      解析请求行。
     *      @fn ParseHeader(); @endcode
     *      解析请求头, 具体只解析了 Host:头部字段、Connection:头部字段、Content-Length:头部字段。
     *      即 主机ip、连接状态、请求内容程度 这三个头部字段。
     *      @fn ParseContent(); @endcode
     *      解析请求内容。
     *      @fn ProcessRequestFile(); @endcode
     *      如果http请求报文请求了文件, 则调用此函数来将请求的文件加载到内存。(即填充 requestFileAddress、
     *      requestFileStat字段)。
     *
     * @fn ProcessHttpResponse(); @endcode 函数内部调用了
     *      @fn AddStatusLine(); @endcode
     *      添加响应状态行。
     *      @fn AddHeader(); @endcode
     *      添加响应头部字段, 具体只添加了 Content-Length:头部字段、Connection:头部字段、@code \r\n @endcode 头部字段。
     *      即: 文本长度、连接状态、空行 这三个头部字段。
     *      @fn AddContent(); @endcode
     *      如果是文件请求, 那么添加响应内容。
     *
     * @since 0.1
     */
    void process() {
        // 解析 http 请求报文。
        RequestMessageParseResult parseResult = ParseHttpRequest();
        // NO_REQUEST, 表示解析的http请求报文不完整, 需要继续由 main线程 读取http请求报文数据。
        if (parseResult == RequestMessageParseResult::NO_REQUEST) {
            // 修改(更新)当前的connFd之前在epoll中注册的监听读事件, 因为请求报文不完整, 还需要继续读取http请求报文数据。
            Epoll::modifyFdFromEpoll( epollFd, connFd, EPOLLIN );

            // 注意, 这里返回后, 即为线程池中的一个线程执行之前从请求队列中取来的任务处理结束了。
            // 这个线程现在继续等待抢请求队列中的任务(如果当前没有任务的话(main线程没有扔任务的话)),
            // 如果有任务, 那么这个线程就抢上处理那个任务。
            // 即一个线程可能拿到的任务是之前某个线程处理的结果, 而之前的那个线程可能就是从这里返回的。
            return;
        }

        // 调用 processHttpResponse()函数, 向浏览器发送响应报文。
        // 根据 主状态机 parseHttpRequest()处理后返回的对一个http请求报文的解析结果。(processRequestFile();的返回的对一个http请求报文解析的结果。)
        // 服务器子线程调用processHttpResponse();向writeBuf中写入响应报文。
        // processHttpRequestResult = GET_REQUEST/FILE_REQUEST/...
        bool processHttpResponseResult = ProcessHttpResponse( parseResult );
        // 如果写响应报文失败了, 那是服务器出现了问题。那么服务器就直接关闭 connFd, 浏览器客户端如果还是需要干什么,
        // 那就重新请求, 然后服务器重新处理。
        if (!processHttpResponseResult) {
            closeConn();
        }

        // 修改(更新)当前的connFd之前在epoll中注册的监听读事件, 因为当前的connFd已经向浏览器发送了响应报文?
        // 应该没有吧(不是应该没有而是一定没有, 在子线程中不会进行发送的, 而是main线程进行发送。),
        // 应该是写的缓冲区中现在写好了响应报文, 然后在epoll中注册connFd期待写的事件。
        // 需要更新connFd在epoll中注册的读事件为写事件。
        // 然后在 main线程 中将写好的响应报文从 writeBuf 中发送到 对端浏览器。
        Epoll::modifyFdFromEpoll( epollFd, connFd, EPOLLOUT );
    }

private:
    /*
     * 主状态机。
     */
    /**
     * @brief 解析http请求报文。
     * @details 通过两个主从有限状态机, 完成http请求报文的解析。此函数为主状态机。从状态机为: ParseRequestMessagePerLine();函数。
     * @return 解析http请求的结果。
     *
     * @since 0.1
     * @note 后续可能对这个版本进行改进。
     */
    RequestMessageParseResult ParseHttpRequest() {
        // 初始化从状态机状态。
        RequestMessagePerLineParseResult lineStatus = RequestMessagePerLineParseResult::LINE_OK;
        char* text = nullptr;

        /*
         * 如果主状态机的状态为 RequestMessage::CONTENT(主状态机的最后一个状态), 并且从状态机也为
         * RequestMessagePerLineParseResult::LINE_OK 的话, 状态机再推一次, 推了这次,
         * case RequestMessage::CONTENT: 中, 这个case中会解析http请求报文的消息体。
         * 解析完消息体即完成报文解析。如果解析消息体成功, 那么就返回了。如果失败,
         * 为了避免再次进入循环(再次进入主状态机), 所以设置lineStatus为 LINE_OPEN。
         * 这样就退出了主状态机了。
         */
        while (
                ((state == RequestMessage::CONTENT) && lineStatus == RequestMessagePerLineParseResult::LINE_OK) ||
                ((lineStatus = ParseRequestMessagePerLine()) == RequestMessagePerLineParseResult::LINE_OK)

                ) {
            // 获取 http请求报文完整的 一行。
            // 因为在 parseRequestMessagePerLine(); 从状态机中已经将 \r\n 改为了 \0\0 了。
            text = GetLine();

            // parseReadBufIdx 在从状态机中已经更新了。
            requestMessagePerLineBegin = parseReadBufIdx;

            fprintf( stdout, "got 1 http line: %s\n", text );

            // state 的第一个状态是 RequestMessage::REQUEST_LINE , 这是在 init(); 中进行初始化的。
            switch (state) {

                case RequestMessage::REQUEST_LINE: {
                    RequestMessageParseResult result = ParseRequestLine( text );
                    if (result == RequestMessageParseResult::BAD_REQUEST) {
                        return RequestMessageParseResult::BAD_REQUEST;
                    }
                    break;
                }
                case RequestMessage::HEADER: {
                    RequestMessageParseResult result = ParseHeader( text );
                    if (result == RequestMessageParseResult::GET_REQUEST) {
                        return ProcessRequestFile();
                    }
                    // todo 注意: 这里没有懂！
                    //  如果是请求内容的话, 那么 break; 之后会再次调用 parseRequestMessagePerLine();从状态机。
                    //  但是从状态机是通过 \r\n 来判断结束了。请求内容没有\r\n的标志！
                    //
                    // todo 懂了, 是 while()循环的两个条件一定要有先后顺序！！！！
                    //  而且一定是 ((state == RequestMessage::CONTENT) && lineStatus == httpRequestMessagePerLineParseResult::LINE_OK)
                    //  在前。
                    //  这样就是 这个 break; switch后, 此时主状态机推到了 RequestMessage::CONTENT 并且
                    //  lineStatus == httpRequestMessagePerLineParseResult::LINE_OK 所以再次进入状态机。
                    //  进入RequestMessage::CONTENT状态中解析请求内容。
                    break;
                }
                case RequestMessage::CONTENT: {
                    RequestMessageParseResult result = ParseContent( text );
                    if (result == RequestMessageParseResult::GET_REQUEST) {
                        return ProcessRequestFile();
                    }

                    // 解析完消息体即完成报文解析，避免再次进入循环，再次进行主状态机，所以设置lineStatus为 LINE_OPEN
                    // 这句代码非常的重要。
                    lineStatus = RequestMessagePerLineParseResult::LINE_OPEN;

                    break;
                }
                default: {
                    return RequestMessageParseResult::INTERNAL_ERROR;
                }

            }
        }

        return RequestMessageParseResult::NO_REQUEST;
    }

private:
    /**
     * @brief requestMessagePerLineBegin是一个http请求报文中的一行在readBuf中的起始位置,
     * 将该位置(起始地址开始)后面的数据(后面的字符串)赋给text, 此时从状态机已提前将一行的末尾字符 @code \r\n @endcode
     * 变为 \0\0，所以调用本函数可以直接取出完整的一行进行解析。即此函数依赖于 ParseRequestMessagePerLine();从状态机函数。
     *
     * @return 返回一行http请求报文行, 是 C风格的字符串。
     *
     * @since 0.1
     */
    char* GetLine() {
        return readBuf + requestMessagePerLineBegin;
    }

private:
    /*
     * 从状态机。
     */
    /**
     * @brief 从状态机。此函数调用是主状态机推动的while循环的条件。
     * <strong>此函数为解析http请求报文时的一个核心函数。</strong>
     * @details 解析 readBuf缓冲区中的 http请求报文。当遇到 @code \r\n @endcode
     * 即解析出了一个http请求报文行。
     * @return 返回解析http请求报文行的结果, 此结果为从状态机的状态。
     *
     * @since 0.1
     */
    RequestMessagePerLineParseResult ParseRequestMessagePerLine() {
        // temp为将要分析的字节
        char temp;
        // 循环解析 readBuf[2048] 中存储的 一个http请求报文的 一行。
        for (; parseReadBufIdx < readBufEnd; ++parseReadBufIdx) {
            temp = readBuf[parseReadBufIdx];

            // 如果\r恰巧是目前readBuf中最后一个已经被读入的客户数据，那么这次解析由于没有读取到http请求报文完整的一行，
            // 就返回LINE_OPEN, 来表示还需要继续读取客户数据(http请求报文)来进一步分析。
            if (temp == '\r') {
                // 如果 \r 是当前 readBuf 中的最后一个有效字符, 那么这次解析由于没有读取到http请求报文完整的一行，
                // 就返回LINE_OPEN
                if ((parseReadBufIdx + 1) == readBufEnd) {
                    return RequestMessagePerLineParseResult::LINE_OPEN;
                }
                    // 如果下一个字符是\n,说明读到一个完整的行
                else if (readBuf[parseReadBufIdx + 1] == '\n') {
                    // 终于找到错的地方了。
                    // 将 \r\n 改为 \0\0, 这样调用 getLine();的时候
                    readBuf[parseReadBufIdx++] = '\0';
                    readBuf[parseReadBufIdx++] = '\0';
                    // 在从状态机中, parseReadBufIdx 的索引即为 http请求报文下一行的 行首。
                    return RequestMessagePerLineParseResult::LINE_OK;
                }
                // 否则，说明http请求报文存在语法问题, 返回 LINE_BAD
                return RequestMessagePerLineParseResult::LINE_BAD;
            }

        }

        // 如果所有内容分析完毕都没有遇到\r字符，则返回LINE_OPEN，表示还需要继续读取客户数据(http请求报文)才能进一步解析。
        return RequestMessagePerLineParseResult::LINE_OPEN;
    }

private:
    /**
     * @brief 解析http请求行，获得请求方法，目标url及http版本号。
     * 例如: GET /index.html HTTP/1.1
     *
     * @details 函数内部调用了几个 C 风格的字符串处理函数:
     * @fn strpbrk(); strcasecmp(); strspn(); strncasecmp(); strchr(); @endcode 共 5 个C风格字符串的处理函数。
     * 这几个函数全部在@headerfile string.h @endcode 中声明。
     *
     * @param text 传入解析的字符串。
     * @return 返回解析请求头部的结果。
     *
     * @since 0.1
     */
    RequestMessageParseResult ParseRequestLine(char* text) {
        // strpbrk是在源字符串（s1）中找出最先含有搜索字符串（s2）中任一字符(注意: 是 s2 中的任意一个字符, 因为
        // s2 也是一个字符串, 也就是说, 会对 s2 字符串中的每个字符进行匹配。)的位置并返回，若找不到则返回空指针。
        // 其原型为: char *strpbrk( char *s1, char *s2)
        // 在HTTP报文中，请求行用来说明请求类型,要访问的资源以及所使用的HTTP版本，其中各个部分之间通过 "\t" 或 " " 分隔。
        // 请求行中最先含有空格和\t任一字符的位置并返回
        url = strpbrk( text, " \t" );
        // 如果请求行中没有空格字符或"\t"字符, 那么HTTP请求报文的请求行有问题, 那么就是这个HTTP请求报文有问题。
        if (url == nullptr) {
            return RequestMessageParseResult::BAD_REQUEST;
        }

        // 将该位置改为"\0"，用于将解析的前面的字符串取出。
        // 这句非常的重要。
        // 先将当前 " " 或者 "\t" 字符设置为 "\0", 表示字符串的结尾。
        // 然后 url再往后移动一个位置。url此时指向了第一个空格或\t字符的下一个位置。即指向请求资源的第一个字符, 例 /index.html 中的 / 字符。
        *url++ = '\0';
        // 解析出来的 http方法。
        char* httpMethod = text;

        // 然后就是判断这个http方法是哪个。
        // 函数声明: int strcasecmp (const char *s1, const char *s2);
        // 函数说明: strcasecmp()用来比较参数s1 和s2 字符串，比较时会自动忽略大小写的差异。
        // 返回值: 若参数s1 和s2 字符串相同则返回0。s1 长度大于s2 长度则返回大于0 的值，s1 长度若小于s2 长度则返回小于0 的值。
        //
        // 取出数据，并通过与GET和POST比较，以确定请求方式。
        // 当时当前只支持 GET 方法, 其它请求都是不支持的。
        if (strcasecmp( httpMethod, "GET" ) == 0) {
            method = Method::GET;
        }/* else if (strcasecmp( httpMethod, "POST" ) == 0) {
        this->method = HttpMethod::POST;
    } */else {
            // 其它的 http方法 当前的服务器都是不支持的。
            return RequestMessageParseResult::BAD_REQUEST;
        }


        // strspn() 函数用来计算字符串 str 中连续有几个字符都属于字符串 accept，
        // 其原型为: size_t strspn(const char *str, const char * accept);
        // url此时跳过了第一个空格或\t字符，现在 url 为 /go.html HTTP/1.1
        // 这里 strspn( url, " \t" ); 返回值为 0, 即 url指向的位置依然是 / 没有改变。
        // 加不加都没有必要。
        //
        // 加是非常有必要的。
        // 因为我们不知道当前url的指向的是否是 /index.html中的 / 字符。
        // GET     /index.html     HTTP/1.1
        // GET 和 /index.html 之间可能有多个 空格或者\t。
        // 所以一定要调用 strspn();来忽略这些空格或者\t。
        url += strspn( url, " \t" );
        // 此时 url 就是真的指向了 /index.html 中的 / 字符了。

        // 使用与判断http方法的相同逻辑，判断HTTP版本号
        // /go.html HTTP/1.1
        httpVersion = strpbrk( url, " \t" );
        if (httpVersion == nullptr) {
            return RequestMessageParseResult::BAD_REQUEST;
        }
        *httpVersion++ = '\0';
        httpVersion += strspn( httpVersion, " \t" );
        // 当执行完 strspn();函数之后, httpVersion指针就指向了, HTTP/1.1中的 H。

        // 服务器仅支持HTTP/1.1, 其它版本会归类为错误的http请求。
        // 为什么这里能直接进行比较呢?
        // 因为 text 最后已经是 \0 了。所以 现在 httpVersion指向的就是一个字符串。
        if (strcasecmp( httpVersion, "HTTP/1.1" ) != 0) {
            return RequestMessageParseResult::BAD_REQUEST;
        }

        // 检查 url 是否合法。
        // 对请求资源前7个字符进行判断
        // 这里主要是有些报文的请求资源中会带有http://, 这7个字符。这里需要对这种情况进行单独处理
        // 例如: http://192.168.52.138:80/index.html 这样的。
        if (strncasecmp( url, "http://", 7 ) == 0) {
            url += 7;
            // strchr() 用来查找某字符在字符串中首次出现的位置，其原型为:
            // char * strchr (const char *str, int c);
            // 返回值为 字符第一次出现在 字符串的中的指针 char* 。
            url = strchr( url, '/' );
            // 当指向完 strchr(); 后 url 才真正指向了 /index.html 中的 / 字符。
        }

        // 如果url为空或者url指向的字符不是'/', 那就是错误的请求行。
        // 一般的不会带有上述两种符号, 直接是单独的/或/后面带访问资源
        if (/* !url || */ url[0] != '/') {
            return RequestMessageParseResult::BAD_REQUEST;
        }

        // 推状态机到 RequestMessage::HEADER 状态。
        state = RequestMessage::HEADER;

        // 当前的一个http请求报文没有解析完, 因为只解析了 RequestMessage::REQUEST_LINE, 是一个不完整, 所以返回 NO_REQUEST。
        return RequestMessageParseResult::NO_REQUEST;
    }

private:
    //      GET /index.html HTTP/1.1
    //      Host: 192.168.52.138
    //      User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:94.0) Gecko/20100101 Firefox/94.0
    //      Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
    //      Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
    //      Accept-Encoding: gzip, deflate
    //      Connection: keep-alive
    //      Upgrade-Insecure-Requests: 1
    //      If-Modified-Since: Sun, 14 Nov 2021 08:02:52 GMT
    //      If-None-Match: "2aa6-5d0bb1e82f32f-gzip"
    //      Cache-Control: max-age=0
    //      \r\n
    /**
     * @brief 解析http请求头部。
     *
     * @details 使用了和 @fn ParseRequestLine(); @endcode 相同的 C 风格字符串的解析函数。
     *
     * @param text 传入解析的字符串。
     * @return 解析的结果。
     *
     * @since 0.1
     */
    RequestMessageParseResult ParseHeader(char* text) {
        // 如果是空行的话, 那么就是头部字段解析完毕了。
        // 判断是空行还是请求头
        if (text[0] == '\0') {
            /* 如果HTTP请求有消息体, 则还需要读取contentLength字节的消息体, 状态机转移到 CHECK_STATE_CONTENT状态。*/
            if (requestMessageContentLen != 0) {
                // POST需要跳转到消息体处理状态
                // 将状态机推到下一个状态: RequestMessage::CONTENT 状态。
                state = RequestMessage::CONTENT;
                // 没有解析完http请求报文需要继续读解析。
                return RequestMessageParseResult::NO_REQUEST;
            }
            /* 否则说明我们已经得到了一个完整的HTTP请求。*/
            return RequestMessageParseResult::GET_REQUEST;
        }
            /* 解析Host头部字段。
             * Host: 192.168.52.138
             * 注意: Host:中的: 后和 192.的1之间可能有多个空格或者\t。
             */
        else if (strncasecmp( text, "Host:", 5 ) == 0) {
            text += 5;
            // 跳过空格和\t字符
            text += strspn( text, " \t" );

            host = text;
        }
            /* 解析Connection头部字段。
             * Connection: keep-alive
             */
        else if (strncasecmp( text, "Connection:", 11 ) == 0) {
            text += 11;
            // 跳过空格和\t字符
            text += strspn( text, " \t" );

            if (strcasecmp( text, "keep-alive" ) == 0) {
                // 如果是长连接，则将isKeepAlive标志设置为true。
                isKeepAlive = true;
            }
        }
            /* 解析Content-Length头部字段。*/
        else if (strncasecmp( text, "Content-Length:", 15 ) == 0) {
            text += 15;
            text += strspn( text, " \t" );
            requestMessageContentLen = atoi( text );
        }

            /* 其它头部字段不解析不处理。*/
        else {
//            printf( "%s 当前版本的服务器不处理该http请求报文头部字段。\n", text );
//            printf( "当前版本的服务器不处理该http请求报文头部字段。\n" );
            // todo : WC 心态崩了！！！！！
//            printf( "-\n" );

        }

        // 因为http请求报文解析到了请求头部, 还没有解析完, 所以返回 NO_REQUEST。
        return RequestMessageParseResult::NO_REQUEST;
    }

private:
    /**
     * @brief 解析http请求内容。
     *
     * @details 我们没有真正解析http请求的消息体, 只是判断它是否被完整地读入了。
     * 消息体不解析，判断一下是否完整读入。
     *
     * @param text 解析的内容。
     * @return 返回处理解析的结果。
     *
     * @since 0.1
     */
    RequestMessageParseResult ParseContent(char* text) const {
        // 判断buffer中是否读取了消息体
        if (readBufEnd >= (requestMessageContentLen + parseReadBufIdx)) {
            text[requestMessageContentLen] = '\0';

            // 至此读完了一个完整的http请求报文。
            return RequestMessageParseResult::GET_REQUEST;
        }

        return RequestMessageParseResult::NO_REQUEST;
    }

private:
    /**
     * @brief 如果是文件请求, 即GET请求, 其实也必定是GET请求。此函数处理请求文件, 将请求文件加载到内存。
     * @return 返回解析的结果。
     *
     * @since 0.1
     */
    RequestMessageParseResult ProcessRequestFile() {
        // 将requestFilePath赋值为网站根目录
        strcpy( requestFilePath, WEBSITE_ROOT_PATH );
        // WEBSITE_ROOT 字符串的长度。
        size_t webSiteRootPathSize = strlen( WEBSITE_ROOT_PATH );

        // 将url与网站根目录拼接
        // url当前为例如: /index.html
        strncpy( requestFilePath + webSiteRootPathSize, url,
                 sizeof( requestFilePath ) - webSiteRootPathSize - 1 );
        // 执行完之后为 "/home/liuyou/CLionProjects/HttpServer2/resource/index.html"

        // 然后对这个请求文件进行解析。requestFilePath当前是这样的, 例如:
        // "/var/www/html/go.html"

        // 通过调用stat();获取请求资源文件信息，成功则将信息更新到requestFileStat结构体字段中。
        // 失败返回NO_RESOURCE状态，表示请求的文件资源不存在。
        if (stat( requestFilePath, &requestFileStat ) < 0) {
            // 若资源页不存在则HTTP解析结果为404
            return RequestMessageParseResult::NO_RESOURCE;
        }

        // 判断文件的权限，是否可读，不可读则返回FORBIDDEN_REQUEST
        if (!(requestFileStat.st_mode & S_IROTH)) {
            // 资源没有权限获取
            return RequestMessageParseResult::FORBIDDEN_REQUEST;
        }

        // 判断文件类型，如果是目录，则返回BAD_REQUEST，表示请求报文有误
        if (S_ISDIR( requestFileStat.st_mode )) {
            // 如果请求为目录, 那么请求报文中的请求行中的请求资源是目录, 即错误的请求, 因为请求的是目录。
            return RequestMessageParseResult::BAD_REQUEST;
        }

        // 以只读方式打开文件, 通过mmap();将该文件映射到内存中, 提高对文件的访问速度。
        int fdRequestFile = open( requestFilePath, O_RDONLY );
        // 将请求文件资源(即资源页文件)映射到内存
        requestFileAddress = static_cast<char*>(mmap( nullptr, requestFileStat.st_size,
                                                      PROT_READ, MAP_PRIVATE,
                                                      fdRequestFile, 0 ));
        // 将文件内容映射到内存中后, 就关掉文件描述符, 避免文件描述符的浪费和占用。因为之后不再使用这个文件描述符了。
        close( fdRequestFile );

        // 资源页请求成功, 表示请求文件存在，且可以访问。
        return RequestMessageParseResult::FILE_REQUEST;
    }

    /* *************************************************************** */
private:
    //      HTTP/1.1 200 OK
    //      Date: Sun, 14 Nov 2021 09:24:25 GMT
    //      Server: Apache/2.4.41 (Ubuntu)
    //      Last-Modified: Sun, 14 Nov 2021 08:02:52 GMT
    //      ETag: "2aa6-5d0bb1e82f32f-gzip"
    //      Accept-Ranges: bytes
    //      Vary: Accept-Encoding
    //      Content-Encoding: gzip
    //      Content-Length: 3138
    //      Keep-Alive: timeout=5, max=100
    //      Connection: Keep-Alive
    //      Content-Type: text/html
    /**
     * @brief 根据传入的http解析的结果, 来处理响应报文。
     * @param httpRequestMessageParseResult 解析http的结果。
     * @return 返回是否处理相应报文成功。
     *
     * @since 0.1
     */
    bool ProcessHttpResponse(RequestMessageParseResult httpRequestMessageParseResult) {

        switch (httpRequestMessageParseResult) {

            // 报文语法有误，400
            case RequestMessageParseResult::BAD_REQUEST: {
                AddStatusLine( 400, ERROR_400_TITLE );
                // 注意: 这里在相应报文头部写了相应报文的内容长度。
                AddHeader( strlen( ERROR_400_FORM ));
                if (!AddContent( ERROR_400_FORM )) {
                    return false;
                }
                break;
            }
                // 服务器上没有请求的资源, 404
            case RequestMessageParseResult::NO_RESOURCE: {
                AddStatusLine( 404, ERROR_404_TITLE );
                AddHeader( strlen( ERROR_404_FORM ));
                if (!AddContent( ERROR_404_FORM )) {
                    return false;
                }
                break;
            }
                // 资源没有访问权限，403
            case RequestMessageParseResult::FORBIDDEN_REQUEST: {
                AddStatusLine( 403, ERROR_403_TITLE );
                AddHeader( strlen( ERROR_403_FORM ));
                if (!AddContent( ERROR_403_FORM )) {
                    return false;
                }
                break;
            }
                // http请求报文的文件存在，200
                /*
                 * 响应报文分为两种:
                 * 一种是请求文件的存在，通过io向量机制iovec，声明两个iovec，第一个指向m_write_buf，第二个指向mmap的地址m_file_address；
                 * 一种是请求出错，这时候只申请一个iovec，指向m_write_buf。
                 *
                 * iovec是一个结构体，里面有两个元素，指针成员iov_base指向一个缓冲区，这个缓冲区是存放的是writev将要发送的数据。
                 * 成员iov_len表示实际写入的长度
                 */
            case RequestMessageParseResult::FILE_REQUEST: {
                AddStatusLine( 200, OK_200_TITLE );

                // 如果请求文件存在
                if (requestFileStat.st_size != 0) {
                    AddHeader( requestFileStat.st_size );

                    // 如果是请求文件, 下面填充 ioVec字段, 即为添加http响应报文的内容主体部分。
                    //
                    // 第一个iovec指针指向响应报文缓冲区，长度为writeIdx
                    // 即 响应报文状态行和响应报文头部的内容。
                    ioVec[0].iov_base = writeBuf;
                    ioVec[0].iov_len = writeIdx;
                    // 第二个iovec指针指向mmap返回的文件指针，长度为请求报文请求的文件的大小
                    // 这个文件即为响应报文的报文主体
                    ioVec[1].iov_base = requestFileAddress;
                    ioVec[1].iov_len = requestFileStat.st_size;
                    ioVecCount = 2;

                    bytesToSend = writeIdx + requestFileStat.st_size;
                    bytesHaveSend = 0;


                    return true;
                }
                    // 如果请求的资源大小为0(即是一个空文件)，则返回空白html文件
                else {
                    const char* const okStr = "<html><body>"
                                              "请求资源为空文件。</body></html>";
                    AddHeader( strlen( okStr ));
                    if (!AddContent( okStr )) {
                        return false;
                    }
                }

                break;
            }
                // 服务器内部错误, 500
            case RequestMessageParseResult::INTERNAL_ERROR: {
                AddStatusLine( 500, ERROR_500_TITLE );
                AddHeader( strlen( ERROR_500_FORM ));
                if (!AddContent( ERROR_500_FORM )) {
                    return false;
                }

                break;
            }
            default: {
                return false;
                break;
            }
        }

        // 除FILE_REQUEST状态外，其余状态只申请一个iovec，指向响应报文缓冲区
        ioVec[0].iov_base = writeBuf;
        ioVec[0].iov_len = writeIdx;
        ioVecCount = 1;

        bytesToSend = writeIdx;
        bytesHaveSend = 0;

        return true;

    }

private:
    /**
     * @brief <strong>添加响应报文时, 调用的核心函数。</strong>
     * <strong>此函数为处理响应报文时的核心函数。</strong>
     *
     * @details 往写缓冲区(writeBuf)中写入待发送的数据(即http响应报文)。
     * 此函数供其它函数内部调用更新writeIdx指针和缓冲区writeBuf中的内容。
     *
     * <strong>使用了 C 语言的 可变参数列表 -> va_list。</strong>
     *
     * @param format 格式化的参数。
     * @param ...
     * @return 返回是否调用成功。
     *
     * @note 这里可以后期改为 template variadic来处理。
     *
     * @since 0.1
     */
    bool AddResponse(const char* format, ...) {
        // 如果写入内容超出writeBuf大小则报错
        if (writeIdx >= sizeof( writeBuf )) {
            return false;
        }

        // 定义可变参数列表
        va_list vaList;
        // 将变量arg_list初始化为传入参数
        va_start( vaList, format );

        // 将format内容输出到writeBuf
        // 将数据format从可变参数列表写入缓冲区写，返回写入数据的长度
        // 重要！
        int writeBytes = vsnprintf( writeBuf + writeIdx, sizeof( writeBuf ) - 1 - writeIdx, format, vaList );
        // 如果写入的数据长度超过缓冲区剩余空间，则报错
        if (writeBytes >= (sizeof( writeBuf ) - 1 - writeIdx)) {
            return false;
        }

        // 更新writeIdx位置, 下次往writeBuf中写时, 从writeIdx开始写。
        writeIdx += writeBytes;

        // 清空可变参列表
        va_end( vaList );

        return true;
    }

private:
    /**
     * @brief 添加http响应报文状态行。例如: HTTP/1.1 200 OK
     * @param status http响应码, 例如 200
     * @param title http响应状态, 例如 OK
     * @return 返回是否调用成功。
     *
     * @since 0.1
     */
    bool AddStatusLine(int status, const char* title) {
        // 例如: HTTP/1.1 200 OK
        return AddResponse( "%s %d %s\r\n", "HTTP/1.1", status, title );
    }

private:
    /**
     * @brief 添加http响应报文请求头部。
     *
     * @details 添加响应报文的报文首部，具体添加文本长度、连接状态和空行。
     *
     * @param contentLength 响应报文的长度。
     * @return 是否添加成功。
     * @since 0.1
     */
    bool AddHeader(long contentLength) {
        // 添加响应报文的文本长度。
        // 即添加Content-Length响应报文头部字段，表示响应报文的长度
        AddContentLength( contentLength );
        // 添加响应报文的连接状态
        AddIsKeepAlive();
        // 添加空行
        AddBlankLine();

        return true;
    }

private:
    /**
     * @brief 添加 Content-Length:头部字段(表示响应报文的长度。)。
     * @param contentLength 响应报文内容主体的长度。
     * @return 是否添加成功。
     * @since 0.1
     * @note 这个函数后期可能会合并到 @fn AddHeader();@endcode 中。
     */
    bool AddContentLength(int contentLength) {
        return AddResponse( "Content-Length: %d\r\n", contentLength );
    }

private:
    /**
     * @brief 添加连接状态，通知浏览器端是保持连接还是关闭。
     * @return 是否添加成功。
     * @since 0.1
     * @note 查看 @fn AddContentLength(); @endcode note的内容。
     */
    bool AddIsKeepAlive() {
        return AddResponse( "Connection: %s\r\n", isKeepAlive ? "keep-alive" : "close" );
    }

private:
    /**
     * @brief 添加空行。即 @code \r\n @endcode。
     * @return 是否添加成功。
     * @since 0.1
     * @note 同上一个成员函数。
     */
    bool AddBlankLine() {
        return AddResponse( "%s", "\r\n" );
    }

private:
    /**
     * @brief 添加响应内容。
     * @param content 传入要添加的响应内容。
     * @return 是否添加成功。
     * @since 0.1
     * @note 这个函数后期会进行重新设计。
     */
    bool AddContent(const char* content) {
        return AddResponse( "%s", content );
    }


public:
    /**
     * @brief 发送http响应报文。
     *
     * @details 该函数解决了大文件传输的问题。
     * <p>因为 connFd 为非阻塞的TCP连接socket文件描述符, 所以对其进行写操作可能会产生一种情况:
     * 如果 TCP写缓冲区 没有空间了, 那么调用 writev();函数会返回 < 0, 但是这是一个假错！
     * 此时解决办法是: 修改 connFd 的epoll事件为 EPOLLOUT事件, 等待下一轮EPOLLOUT事件。
     * 那么在此期间, 服务器main线程无法接受到同一客户的下一个请求(因为采用的EPOLLONESHOT), 但是这可以保证连接的完整性。</p>
     *
     * <p>如果将http请求报文全部写完后, 如果http是长连接, 那么会重新为 connFd 注册 EPOLLIN事件。</p>
     *
     * <p>如何判断发送完了http响应报文? 这里我是使用(http响应报文的长度(响应状态行和响应头部) + http请求报文请求文件的长度)
     * 的长度赋值给字段 bytesToSend 。根据字段 bytesToSend 是否为 0, 判断是否发送完了。</p>
     *
     * @return 返回是否将http响应报文发送成功。
     *
     * @since 0.1
     */
    bool write() {
        while (true) {
            /*
                On  success,  readv(), preadv() and preadv2() return the number of bytes read; writev(), pwritev() and pwritev2() return the number
                of bytes written.
                成功时，readv()、preadv() 和 preadv2() 返回读取的字节数； writev()、pwritev() 和 pwritev2() 返回写入的字节数。

                Note that it is not an error for a successful call to transfer fewer bytes than requested (see read(2) and write(2)).
                请注意，成功调用传输的字节数少于请求的字节数并不是错误（请参阅 read(2) 和 write(2)）。

                On error, -1 is returned, and errno is set appropriately.
                出错时，返回 -1，并适当设置 errno。
             */
            ssize_t writeBytes = writev( connFd, ioVec, ioVecCount );
//            if (writeBytes == -1) {
            if (writeBytes < 0) {
                if (errno == EAGAIN) {
                    fprintf( stdout, "======[ tcp发送缓冲区已满 ]=====\n" );

                    if (bytesHaveSend >= ioVec[0].iov_len) {
                        //不再继续发送头部信息
                        ioVec[0].iov_len = 0;
                        ioVec[1].iov_base = requestFileAddress + (bytesHaveSend - writeIdx);
                        ioVec[1].iov_len = bytesToSend;
                    } else {
                        ioVec[0].iov_base = writeBuf + bytesHaveSend;
                        ioVec[0].iov_len = ioVec[0].iov_len - bytesHaveSend;
                    }

                    Epoll::modifyFdFromEpoll( epollFd, connFd, EPOLLOUT );
                    return true;

//                    sleep( 1 );
//                    continue;

                }

                fprintf( stdout, "writev(): %s.\n", strerror(errno));
                Unmap();
                return false;
            }

            bytesHaveSend += writeBytes;
            bytesToSend -= writeBytes;
//            if (bytesToSend <= 0) {
            if (bytesToSend == 0) {
                break;
            }

            if (bytesHaveSend >= ioVec[0].iov_len) {
                //不再继续发送头部信息
                ioVec[0].iov_len = 0;
                ioVec[1].iov_base = requestFileAddress + (bytesHaveSend - writeIdx);
                ioVec[1].iov_len = bytesToSend;
            } else {
                ioVec[0].iov_base = writeBuf + bytesHaveSend;
                ioVec[0].iov_len = ioVec[0].iov_len - bytesHaveSend;
            }

        }

        Unmap();
        fprintf( stdout, "%d 将内容写完了。\n", connFd );
        if (isKeepAlive) {
            fprintf( stdout, "这个连接是长连接。\n" );
            // 重新初始化HTTP对象
            Init();

            // 在epoll树上重置EPOLLONESHOT事件
            Epoll::modifyFdFromEpoll( epollFd, connFd, EPOLLIN );
            return true;
        } else {
            return false;
        }

    }


private:
    /**
     * @brief 当一个http响应报文成功或者不成功发送发送之后, 调用此函数来释放之前解析http请求报文时加载到内存中的
     * http请求的文件。
     *
     * @since 0.1
     */
    void Unmap() {
        // 如果 mmap();调用的结果不为 nullptr, 那么就要销毁这块内存。
        if (requestFileAddress) {
            munmap( requestFileAddress, requestFileStat.st_size );
            requestFileAddress = nullptr;
        }
    }

public:
    HttpConn() = default;

    ~HttpConn() = default;

public:
    /** epoll文件描述符。*/
    static int epollFd;

    /** http请求的数量。*/
    static int userCount;

    /** 请求资源文件的根路径。*/
    constexpr static const char* const WEBSITE_ROOT_PATH = "/home/liuyou/CLionProjects/http-server/src/main/resource";

//private:
public:
    /** 与一个浏览器客户端连接使用的 connFd 。*/
    int connFd = -1;

    /** 浏览器客户端的地址。*/
    sockaddr_in clientAddress;

private:
    /** http请求报文存储在这个缓冲区中。*/
    char readBuf[2048];

    /** 指向读缓冲区(即readBuf[2048]数组)中已经读入的客户发来的数据的最后一个字节的下一个位置。*/
    int readBufEnd;

    /** 指向readBuf[2048]当前正在解析的字符所在的数组下标索引。*/
    int parseReadBufIdx;

    /** 当前正在解析的http请求报文中的一行在readBuf缓冲区中的起始地址。*/
    int requestMessagePerLineBegin;

private:
    /** 主状态机的状态。即解析请求报文到了哪个状态。*/
    RequestMessage state;

    /** http请求的方法, 当前版本仅仅支持 GET 方法。*/
    Method method;

#ifdef http_request_message_format
    //      GET /index.html HTTP/1.1
    //      Host: 192.168.52.138
    //      User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:94.0) Gecko/20100101 Firefox/94.0
    //      Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
    //      Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
    //      Accept-Encoding: gzip, deflate
    //      Connection: keep-alive
    //      Upgrade-Insecure-Requests: 1
    //      If-Modified-Since: Sun, 14 Nov 2021 08:02:52 GMT
    //      If-None-Match: "2aa6-5d0bb1e82f32f-gzip"
    //      Cache-Control: max-age=0
#endif
private:
    /** http请求报文的请求资源。*/
    char* url;

    /** 请求文件在服务器主机上的根路径。*/
    char requestFilePath[1024];

    /** http请求的版本。*/
    char* httpVersion;

    /** Host: 字段。*/
    char* host;

    /** Connection: 字段。*/
    bool isKeepAlive;

    /** Content-Length: 字段。*/
    int requestMessageContentLen;

    /** 请求文件加载到内存中的地址。*/
    char* requestFileAddress;

    /** 请求文件的状态。*/
    struct stat requestFileStat;

#ifdef http_response_message_format
    //      HTTP/1.1 200 OK
    //      Date: Sun, 14 Nov 2021 09:24:25 GMT
    //      Server: Apache/2.4.41 (Ubuntu)
    //      Last-Modified: Sun, 14 Nov 2021 08:02:52 GMT
    //      ETag: "2aa6-5d0bb1e82f32f-gzip"
    //      Accept-Ranges: bytes
    //      Vary: Accept-Encoding
    //      Content-Encoding: gzip
    //      Content-Length: 3138
    //      Keep-Alive: timeout=5, max=100
    //      Connection: Keep-Alive
    //      Content-Type: text/html
#endif
private:
    /** http响应报文的 响应状态行 + 响应头部。*/
    char writeBuf[1024];

    /** 当前写入的数据所在 writeBuf[1024] 中的下标索引。*/
    int writeIdx;

    /** 使用 结构体 struct iovec 配合 writev();来集中写响应报文。*/
    struct iovec ioVec[2];
    /** ioVec数组使用了几个元素。*/
    int ioVecCount;

    /** 要发送一个完整的http响应报文所要发送的字节数。*/
    long bytesToSend;

    /** main线程已经发送的字节数。*/
    long bytesHaveSend;

};


#endif //HTTP_SERVER_HTTPCONN_HPP
